'use client'; import './style.scss'; import 'animate.css'; import { use, useEffect, useState } from 'react'; import { useDonationAlert } from '@/hooks/useDonationAlert'; import { DonationAlertConfig, DonationAlertData } from '@/types/donation'; import { fetchApi } from '@/lib/utils/client'; import View from './view'; type Props = { params: Promise<{ widgetToken: string }>; searchParams: Promise<{ [key: string]: string|string[]|undefined }>; }; const DEFAULT_CONFIG: DonationAlertConfig = { id: 0, title: '', amount: 0, matchType: 0, message: '', playDelaySec: 0, displayDurationSec: 10, popupEffect: null, textEffect: null, nicknameFontFamily: null, nicknameFontSize: 24, nicknameFontColor: '#FFD700', amountFontFamily: null, amountFontSize: 24, amountFontColor: '#FF6B35', messageFontFamily: null, messageFontSize: 18, messageFontColor: '#FFFFFF', templateFontFamily: null, templateFontSize: 24, templateFontColor: '#FFFFFF', enableImage: false, imageUrl: null, enableSound: false, soundUrl: null, isActive: true }; function matchConfig(configs: DonationAlertConfig[], amount: number): DonationAlertConfig { // 1순위: Exact 매칭 (MatchType === 1) const exact = configs.find(c => c.matchType === 1 && c.amount === amount && c.isActive); if (exact) { return exact; } // 2순위: MinThreshold (MatchType === 0) — 금액 이상 중 가장 높은 것 const thresholds = configs .filter(c => c.matchType === 0 && c.amount <= amount && c.isActive) .sort((a, b) => b.amount - a.amount); return thresholds[0] ?? DEFAULT_CONFIG; } export default function AlertPage({ params, searchParams }: Props) { const { widgetToken } = use(params); const sp = use(searchParams); const isPreview = sp.preview === '1'; const hubUrl = process.env.NEXT_PUBLIC_API_URL + '/hubs/donation'; const { current, remoteState, onAlertComplete } = useDonationAlert(widgetToken, hubUrl); const [configs, setConfigs] = useState([]); const [previewConfig, setPreviewConfig] = useState(null); // API에서 config 목록 로드 useEffect(() => { fetchApi<{ list: DonationAlertConfig[] }>(`/api/widget/alert/config/${widgetToken}`, { silent: true }).then(res => { if (res.success && res.data?.list) { setConfigs(res.data.list); } }).catch(() => {}); }, [widgetToken]); // postMessage 수신 (미리보기 모드) const [testAlert, setTestAlert] = useState(null); useEffect(() => { if (!isPreview) { return; } const handler = (event: MessageEvent) => { if (event.origin !== window.location.origin) { return; } if (event.data?.type === 'ALERT_PREVIEW') { setPreviewConfig({ ...DEFAULT_CONFIG, ...event.data.config, }); } if (event.data?.type === 'ALERT_TEST') { setTestAlert({ alertID: Date.now(), donationID: 0, correlationID: '', sponsorMemberID: 0, sendName: event.data.sendName || '테스트유저', amount: event.data.amount || 1000, netAmount: event.data.amount || 1000, message: event.data.message || null, channelID: 0, channelName: '', crewMemberID: null, crewMemberNickname: null, isTest: true, createdAt: new Date().toISOString() }); } }; window.addEventListener('message', handler); return () => window.removeEventListener('message', handler); }, [isPreview]); // 미리보기 테스트 알림 or 실제 알림 const activeAlert = isPreview ? testAlert : current; const config = activeAlert ? (previewConfig ?? matchConfig(configs, activeAlert.amount)) : null; const handleComplete = () => { if (isPreview) { setTestAlert(null); } else { onAlertComplete(); } }; return (
{activeAlert && config && ( )}
); }